#include "MyBoot.h"

//声明一个函数指针,用于跳转PC指针
load_a load_A;


//进入命令行终端模式，实现交互功能
//参数：timeout ：超时等待时间
//返回值： 1 进入终端模式    0 不进入终端模式
uint8_t MyBootLoader_EnterCMD(uint16_t timeout)
{
	//输出提示信息
	printf("在%d ms内,输入字符't'进入终端模式\r\n",timeout);
	//printf("In %d ms,Input /'t/',Will Enter Terminal \r\n",timeout);
	//循环等待
	while(timeout--)
	{
		delay_1ms(1);
		//如果接收到了输入t，则进入命令行终端模式
		if(U0_RxBuff[0] == 't')
		{
			return 1;
		}
	}
	//不进入命令行终端模式
	return 0;
}


//BootLoade分支判断
void MyBootLoader_Branch(void)
{
	//不进入终端模式
	if(MyBootLoader_EnterCMD(2000)==0)
	{
		//如果有OTA事件
		if(OTA_info.OTA_Flag == OTA_NEED_UPDATA)
		{
			printf("OTA 更新!!!\r\n");
			//挂起更新标志位
			BootStatuesFlag |= UPDATA_APPLICATION_FLAG;
			//W25Q64_xBlock设置为0，表示要更新应用程序
			updataA.W25Q64_xBlock = 0;
		}
		//如果没有OTA事件
		else
		{
			printf("跳转至应用程序 \r\n");
			//跳转到应用程序分区
			Load_Application(GD32_A_SADDR);
		}
	}
	printf("进入命令行控制模式\r\n");
	//显示菜单
	MyBootLoader_Menu();
}

//设置SP指针，指向addr处
//  __asm 表示是使用汇编
void MSR_SP(uint32_t addr)
{
	
	//将addr加载到MSP(主堆栈指针)中
	__ASM("MSR MSP, r0");
	//跳回原地址
	__ASM("BX R14");
	
}


//跳转到应用程序
//参数：addr：程序起始地址
void Load_Application(uint32_t addr)
{
	
	//入口参数检查
	if( (*(uint32_t*)addr>=0x20000000) && (*(uint32_t*)addr<=0x20004fff) )
	{
		
		//设置堆栈指针
		//MSR_SP(*(uint32_t*)addr);
		//设置PC指针到复位向量处
		load_A = (load_a) (*(uint32_t*)(addr+4));
		printf("开始清理外设\r\n");	
		//清理外设
		BootLoader_Clear();
		//执行函数指针，相当于设置pc指针
		load_A();
	}
	printf("跳转至应用程序失败\r\n");
}

//跳转前复位外设
void BootLoader_Clear(void)
{
	usart_deinit(USART0);
	spi_i2s_deinit(MYSPI_SPIX);
	gpio_deinit(GPIOA);
	gpio_deinit(GPIOB);
}

//命令行菜单
void MyBootLoader_Menu(void)
{
	printf("\r\n");
	printf("******************************\r\n");
//	printf("[1]erase flash application\r\n");
//	printf("[2]Usart IAP load application \r\n");
//	printf("[3]Set OTA Version\r\n");
//	printf("[4]Check Version\r\n");
//	printf("[5]Download the program to the external flash\r\n");
//	printf("[6]Use the external Flash internal program\r\n");
//	printf("[7]ReStart\r\n");
	
	printf("[1]擦除flash程序\r\n");
	printf("[2]串口IAP下载A区应用程序\r\n");
	printf("[3]设置OTA版本号\r\n");
	printf("[4]查看OTA版本号\r\n");
	printf("[5]向外部flash下载程序\r\n");
	printf("[6]使用外部flash中的程序\r\n");
	printf("[7]重启\r\n");
	printf("[8]查看外部flash内存占用\r\n");
	printf("******************************\r\n");
	printf("\r\n");
}


//BootLoader事件处理函数
void MyBootLoader_Event(uint8_t* data,uint32_t datalen)
{
	//用于循环，方便计算偏移量
	int i=0;
	
	//空闲时才执行
	if(BootStatuesFlag == 0)
	{
		printf("\r\n");
		//参数检查，并执行对应的命令
		if( (datalen==1)&&(data[0]=='1') )
		{
			printf("擦除flash程序\r\n");
			//擦除A区
			GD32_EraseFlash(GD32_A_START_PAGE,GD32_A_PAGE_NUM);
			MyBootLoader_Menu();
		}
		else if( (datalen==1)&&(data[0]=='2') )
		{
			printf("Xmodel串口IAP下载A区应用程序,请使用Bin格式文件\r\n");
			//擦除A区
			GD32_EraseFlash(GD32_A_START_PAGE,GD32_A_PAGE_NUM);
			//置位Xmodem发送C标准位
			BootStatuesFlag |= (IAP_XMODEM_SENDC_FLAG | IAP_XMODEM_REC_DATA_FLAG);
			//清零延迟
			updataA.XmodemTimer = 0;
			//清零数据包计数
			updataA.Xmodem_xPacket = 0;
		}
		else if( (datalen==1)&&(data[0]=='3') )
		{
			printf("设置OTA版本号，请按照以下格式输入\r\n");
			printf("VER-1.0.0-2025/7/4");
			//挂起版本号设置标志位
			BootStatuesFlag |= VERSION_SET_FLAG;
			
		}
		else if( (datalen==1)&&(data[0]=='4') )
		{
			printf("\r\n");
			printf("当前OTA版本号:%s \r\n",OTA_info.Version);
			printf("\r\n");
			//显示菜单
			MyBootLoader_Menu();
			
		}
		else if( (datalen==1)&&(data[0]=='5') )
		{
			printf("Xmodel串口IAP下载应用程序至外部flash,请输入要下载到外部flash的块号(1~9)\r\n");
			//置位指定下载位置块号标志位
			BootStatuesFlag |= NEED_XBLOCK_FLAG;					
		}
		else if( (datalen==1)&&(data[0]=='6') )
		{
			printf("使用外部flash中的程序，请输入要使用的程序的块号(1~9)\r\n");
			//置位从外部flash下载程序标志位
			BootStatuesFlag |= LOAD_APP_FROM_EXTERN_FLAG;
		}
		else if( (datalen==1)&&(data[0]=='7') )
		{
			printf("10ms后重启\r\n");
			delay_1ms(10);
			nvic_system_reset();
		}
		else if( (datalen==1)&&(data[0]=='8') )
		{
			printf("外部flash内存占用如下\r\n");
			for(int i=1;i<10;i++)
			{
				printf("分区[%d]:%d字节\r\n",i,OTA_info.FireLen[i]);
			}
			//显示菜单
			MyBootLoader_Menu();
		}
		
	}
	//如果IAP接收标志位被置位
	else if(BootStatuesFlag & (IAP_XMODEM_REC_DATA_FLAG | LOAD_IN_EXTERN_FLASH_FLAG) )
	{
		//判断是否是Xmodem的数据包
		if( (datalen == 133) && (data[0]) == 0x01 )
		{
			//开始接收，并停止发送C
			BootStatuesFlag &= (~IAP_XMODEM_SENDC_FLAG);
			//校验接收到的数据 排除包头
			updataA.XmodemCRC = Xmodem_CRC16(&data[3],128);
			//匹配CRC校验值
			if(updataA.XmodemCRC == ( (data[131]<<8) + data[132]) )
			{
				//记录包数
				updataA.Xmodem_xPacket++;
				//将数据移到更新缓存区
				memcpy(&updataA.Updata_buffer[(updataA.Xmodem_xPacket-1)%(GD32_PAGE_SIZE/128)*128],&data[3],128);
				//如果凑够了一个扇区
				if( (updataA.Xmodem_xPacket%(GD32_PAGE_SIZE/128))  == 0)
				{
					//判断是更新A区,还是写入到外部flash
					if( BootStatuesFlag & LOAD_IN_EXTERN_FLASH_FLAG)
					{
						//W25Q64一页是256字节,所以1K的数据要分页写入
						for(i=0;i<GD32_PAGE_SIZE/256;i++)
						{
							//目标地址=块偏移+包偏移+页偏移
							MyW25Q64_PageWrite(&updataA.Updata_buffer[i*256],updataA.W25Q64_xBlock*64*1024/256+(updataA.Xmodem_xPacket/8-1)*4+i);
						}
					}
					else
					{
						//按页写入到A区
						GD32_WriteFlash(GD32_A_SADDR + ((updataA.Xmodem_xPacket/(GD32_PAGE_SIZE/128))-1)*GD32_PAGE_SIZE ,(uint32_t*)updataA.Updata_buffer,GD32_PAGE_SIZE);				
					}
				}
				//发送应答
				printf("\x06");
			}
			else
			{
				//发送非应答
				printf("\x15");
			}
		}				
		//如果接收到了包尾
		else if( (datalen == 1)&&(data[0] == XMODEM_EOT) )
		{
			//发送包尾标志
			printf("\x06");
			//如果缓冲区内还有剩余，再全部写入非整页内容
			if( ((updataA.Xmodem_xPacket)%(GD32_PAGE_SIZE/128)) != 0)
			{
				//判断是更新A区,还是写入到外部flash
				if( BootStatuesFlag & LOAD_IN_EXTERN_FLASH_FLAG)
				{
					for(i=0;i<4;i++ )
					{
						MyW25Q64_PageWrite(&updataA.Updata_buffer[i*256],updataA.W25Q64_xBlock*64*1024/256+(updataA.Xmodem_xPacket/8)*4+i);
					}
				}
				else
				{
					//写入到A区
					GD32_WriteFlash(GD32_A_SADDR + (updataA.Xmodem_xPacket/(GD32_PAGE_SIZE/128))*GD32_PAGE_SIZE ,(uint32_t*)updataA.Updata_buffer,(updataA.Xmodem_xPacket)%(GD32_PAGE_SIZE/128)*128);

				}
			}
			//判断是更新A区,还是写入到外部flash
			if( BootStatuesFlag & LOAD_IN_EXTERN_FLASH_FLAG)
			{
				delay_1ms(10);
				//清除标志位
				BootStatuesFlag &= (~LOAD_IN_EXTERN_FLASH_FLAG);
				
				//保存信息
				OTA_info.FireLen[updataA.W25Q64_xBlock] = updataA.Xmodem_xPacket*128;
				MyW24C02_Write_BootloaderInfo();
				printf("写入块%d成功\r\n",updataA.W25Q64_xBlock);
				printf("\r\n");
			}
			else
			{
				//复位接收标志位
				BootStatuesFlag &= (~IAP_XMODEM_REC_DATA_FLAG);
				printf("10ms后重启\r\n");
				printf("\r\n");
				delay_1ms(10);
				nvic_system_reset();
			}
			MyBootLoader_Menu();
		}
	}
	//如果写入版本号标志位被置位
	else if(BootStatuesFlag & VERSION_SET_FLAG)
	{
		if(datalen == 18)
		{
			//格式提取值，不关心，所以仅使用一个变量
			int temp;
			//格式化匹配
			if(sscanf((char *)data,"VER-%d.%d.%d-%d/%d/%d",&temp,&temp,&temp,&temp,&temp,&temp)==6 )
			{
				//存储前先清空
				memset(OTA_info.Version,0,32);
				//刷新版本号
				memcpy(OTA_info.Version,data,18);
				//写入到外部EEPROM中
				MyW24C02_Write_BootloaderInfo();
				printf("版本号正确，已保存\r\n");
				printf("当前版本号:%s\r\n",OTA_info.Version);
				//清除写入版本号标志位
				BootStatuesFlag &= (~VERSION_SET_FLAG);
				//显示菜单
				MyBootLoader_Menu();
			}
			else
			{
				printf("版本号格式错误，请按照以下格式输入\r\n");
				printf("VER-1.0.0-2025/7/4\r\n");
			}
		}
		else
		{
			printf("版本号长度错误，请重新输入\r\n");
		}
	}
	//如果指定下载位置块号标志位被置位
	else if(BootStatuesFlag & NEED_XBLOCK_FLAG)
	{
		//入口参数检查
		if( datalen ==1 )
		{
			if( (data[0] >= '1' )&&( data[0]<='9' ))
			{
				printf("%c\r\n",data[0]);
				//记录块序号
				updataA.W25Q64_xBlock = data[0]- '0'  ;				
				//置位Xmodem发送C标准位以及向外部flash下载程序标志位
				BootStatuesFlag |= (IAP_XMODEM_SENDC_FLAG | LOAD_IN_EXTERN_FLASH_FLAG);
				//清零延迟
				updataA.XmodemTimer = 0;
				//清零数据包计数
				updataA.Xmodem_xPacket = 0;
				//清零块内字节计数
				OTA_info.FireLen[updataA.W25Q64_xBlock]=0;
				printf("开始擦除块%d的程序，请等待\r\n",updataA.W25Q64_xBlock);
				//擦除块内程序
				MyW25Q64_Erase64K(updataA.W25Q64_xBlock);
				//清除指定下载位置块号标志位
				BootStatuesFlag &= (~NEED_XBLOCK_FLAG);
				printf("Xmodel串口IAP下载应用程序至外部flash的%d块,请使用Bin格式文件\r\n",updataA.W25Q64_xBlock);
			}
			else
			{
				printf("指定块号错误(1~9)，请重新输入\r\n");
			}			
		}
		else
		{
			printf("指定块号长度错误(1~9)，请重新输入\r\n");
		}
	}
	//如果从外部flash下载程序标志位被置位
	else if(BootStatuesFlag & LOAD_APP_FROM_EXTERN_FLAG)
	{
		if(datalen == 1)
		{
			if( (data[0] >= '1' )&&( data[0]<='9' ))
			{
				updataA.W25Q64_xBlock = (data[0] - '0');
				BootStatuesFlag |= UPDATA_APPLICATION_FLAG;
				BootStatuesFlag &= (~LOAD_APP_FROM_EXTERN_FLAG);
			}
			else
			{
				printf("指定块号错误(1~9)，请重新输入\r\n");
			}			
		}
		else
		{
			printf("指定块号长度错误(1~9)，请重新输入\r\n");
		}
	}
	

}

//Xmodem协议CRC16位校验
uint16_t Xmodem_CRC16(uint8_t *data,uint16_t datalen)
{
	//循环变量
	uint8_t i=0;
	//CRC初始值
	uint16_t CrcInitValue = 0x0000;
	//CRC多项式
	uint16_t Crcipoly = 0x1021;
	
	//逐个校验
	while(datalen--)
	{
		//循环异或
		CrcInitValue	=(*data<<8) ^ CrcInitValue;
		for(i=0;i<8;i++)
		{
			// '1'探测器
			if(CrcInitValue & 0x8000)
			{
				//反相器运算(多项式'1'位相关)
				CrcInitValue = (CrcInitValue << 1) ^ Crcipoly;
			}
			else
			{
				//不反相，左移一位
				CrcInitValue = (CrcInitValue<<1);
			}
		}
		//指针移到下一个数据
		data++;
	}
	//返回校验和
	return CrcInitValue;
}

